"""
@Time ： 2022/10/30 10:59
@Auth ： lxd
@File ：autompg.py
"""
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# PLT 绘制图片
import matplotlib.pyplot as plt
# panadas 数据处理用库
import pandas as pd
# 多个表格联合显示
import seaborn as sns

import tensorflow as tf
from tensorflow.python import keras
from tensorflow.python.keras import layers, optimizers, Sequential

from tensorflow.python.keras.utils import data_utils
# 回调函数
from tensorflow.python.keras import callbacks

# 显示所有列
pd.set_option('display.max_columns', None)
# 显示所有行
pd.set_option('display.max_rows', None)
# 设置value的显示长度为100，默认为50
pd.set_option('max_colwidth', 30)
print(tf.__version__)

# 在线下载汽车效能数据集
dataset_path = data_utils.get_file("auto-mpg.data",
                                   "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
# pands列名：“MPG”、“气缸”、“排量”、“马力”、“重量”、“加速”、“车型年份”、“原产地”。
column_names = ['MPG', 'Cylinders', 'Displacement', 'Horsepower', 'Weight', 'Acceleration', 'Model Year', 'Origin']

# 将data格式数据读入内存 并加上每一列的名字
# skipinitialspace : 忽略分隔符后的空白（默认为False，即不忽略）.
# na_values:原数据中有数据缺失为？，na_values='?'可以将？标明为NAN（未知值）。
# comment:标识着多余的行不被解析,='\t'的意思是忽略以\t（一个制表符）开头后的行内容，这部分内容不读入。
# sep:指定分隔符。如果不指定参数，则会尝试使用逗号分隔。
raw_dataset = pd.read_csv(dataset_path, names=column_names,
                          na_values="?", comment='\t',
                          sep=" ", skipinitialspace=True)

# 复制数据集
dataset = raw_dataset.copy()
dataset.tail()
# 看最末尾5项数据
print(dataset.tail())
# 最开始5项数据
# print(dataset.head())

# 数据预处理部分
# isna()函数用于检测缺失值,统计有哪些数据不全
dataset.isna().sum()
print(dataset.isna().sum())
# 去掉数据中不全的行
dataset = dataset.dropna()

# 将地区分类 改成onehot方式
origin = dataset.pop("Origin")
dataset['USA'] = (origin == 1) * 1.0
dataset['Europe'] = (origin == 2) * 1.0
dataset['Japan'] = (origin == 3) * 1.0
print(dataset.tail())

# 采用sample函数进行训练集分割，其中frac参数为分割比例，random_state=0意思取得的数据不重复，如果等于1则是代表取得的数据可以重复。
# 测试集就是训练集剩下的部分，我们用drop函数移除训练集的部分，剩下的部分自然就是测试集，并用变量保存起来。
train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)

# sns.pairplot用来展示特征之间的关系
sns.pairplot(train_dataset[["MPG", "Cylinders", "Displacement", "Weight"]], diag_kind="None")
plt.show()

# 这里运行如果出现 TypeError: Cannot interpret '<attribute 'dtype' of 'numpy.generic' objects>' as a data type
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -U numpy
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -U pandas


# 统计各种数据 并且转置易于观察
train_stats = train_dataset.describe()
train_stats.pop("MPG")
train_stats = train_stats.transpose()
print(train_stats)

# 将油耗作为标签项 剥离 ，将特征值从目标值或者"标签"中分离。 这个标签是你使用训练模型进行预测的值。
train_labels = train_dataset.pop("MPG")
test_labels = test_dataset.pop("MPG")


# 制作归一化函数 这里用的是Zscore方法
def norm(x):
    return (x - train_stats['mean']) / train_stats['std']


# 标准化之后的数据集
normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset)


# 实际项目中要注意保存相关的train_stats 数据 以便于后续应用时候数据处理

# 函数化建模
def build_model():
    model = Sequential([
        layers.Dense(64, activation=tf.nn.relu, input_shape=[len(train_dataset.keys())]),
        layers.Dense(64, activation=tf.nn.relu),
        layers.Dense(1)
    ])

    optimizer = optimizers.adam_v2.Adam(learning_rate=1e-3)
    # keras.optimizers.rmsprop_v2.RMSprop()
    model.compile(loss='mean_squared_error',
                  optimizer=optimizer,
                  metrics=['mean_absolute_error', 'mean_squared_error'])  # 度量的参数为mae（绝对值误差）和mse（均方误差）的loss
    return model


model = build_model()
model.summary()

# 现在试用下这个模型。从训练数据中批量获取‘10’条例子并对这些例子调用 model.predict 。
example_batch = normed_train_data[:10]  # 取前10个数据
print(example_batch)
example_result = model.predict(example_batch)  # 预测
print(example_result)

# 回调函数编写
# class PrintDot(keras.callbacks.Callback):
#     @staticmethod
#     def on_epoch_end(epoch, logs):
#         if epoch % 100 == 0: print('')
#         print('t', end='')

EPOCHS = 300

# verbose：日志显示,verbose = 0 为不在标准输出流输出日志信息,verbose = 1 为输出进度条记录,verbose = 2 为每个epoch输出一行记录
history = model.fit(
    normed_train_data, train_labels,
    epochs=EPOCHS, validation_split=0.2, verbose=0
)

# 通过pd加载训练的历史数据
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
print(hist.tail())


def plot_history(history):
    hist = pd.DataFrame(history.history)
    hist['epoch'] = history.epoch
    #
    plt.figure()
    plt.xlabel('Epoch')
    plt.ylabel('Mean Abs Error [MPG]')
    plt.plot(hist['epoch'].to_numpy(), hist['mean_absolute_error'].to_numpy(),
             label='Train Error')
    plt.plot(hist['epoch'].to_numpy(), hist['val_mean_absolute_error'].to_numpy(),
             label='Val Error')
    plt.ylim([0, 5])
    plt.legend()
    #
    plt.figure()
    plt.xlabel('Epoch')
    plt.ylabel('Mean Square Error [$MPG^2$]')
    plt.plot(hist['epoch'].to_numpy(), hist['mean_squared_error'].to_numpy(),
             label='Train Error')
    plt.plot(hist['epoch'].to_numpy(), hist['val_mean_squared_error'].to_numpy(),
             label='Val Error')
    plt.ylim([0, 20])
    plt.legend()
    plt.show()


plot_history(history)
model = build_model()
# 提前终止训练
# monitor: 监控的数据接口，有’acc’,’val_acc’,’loss’,’val_loss’等等。正常情况下如果有验证集，就用’val_acc’或者’val_loss’。
# patience：能够容忍多少个epoch内都没有improvement
early_stop = callbacks.EarlyStopping(monitor='val_loss', patience=5)

history = model.fit(normed_train_data, train_labels, epochs=EPOCHS,
                    validation_split=0.2, verbose=0, callbacks=[early_stop])
plot_history(history)

# 用没见过的数据做评估
loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=0)
print("Testing set Mean Abs Error: {:5.2f} MPG".format(mae))

# 使用测试集中的数据预测 MPG 值,将数据展平成一行
test_predictions = model.predict(normed_test_data).flatten()
# 显示对比
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
plt.axis('equal')
plt.axis('square')
plt.xlim([0, plt.xlim()[1]])
plt.ylim([0, plt.ylim()[1]])
_ = plt.plot([-100, 100], [-100, 100])
plt.show()

# 误差分布。
error = test_predictions - test_labels
plt.hist(error, bins=25)
plt.xlabel("Prediction Error [MPG]")
_ = plt.ylabel("Count")
plt.show()
